﻿using BacteriumWatcher.Infrastructure;
using System.Windows.Input;
using System;
using System.Collections.Generic;
using System.Threading;
using System.ServiceModel.Discovery;
using System.ServiceModel;
using CommonDataContracts;
using SharedDataContracts;
using BacteriumObserver;
using System.Linq;
using System.Windows;
using GraphSharp.Sample;
using GraphSharp.Controls;
using System.Windows.Threading;
using System.Timers;
using BacteriumWatcher.BacteriumGraph;

namespace BacteriumWatcher.ViewModel
{
    public class BacteriumGraphLayout : GraphLayout<BacteriumSnapshot, BacteriumInteractionEdge, BacteriumInteractionGraph> { }

    public class BacteriumWatcherViewModel : ObservableObject
    {
       
        private List<BacteriumProfile> _bacteria = new List<BacteriumProfile>();        
        private Object _cs = new Object();
        private System.Timers.Timer _pollingTimer = new System.Timers.Timer(5000);

        private BacteriumInteractionGraph _bacteriumGraph = new BacteriumInteractionGraph();      
        
        public List<BacteriumProfile> Bacteria
        {
            get { return _bacteria; }
            set 
            { 
                _bacteria = value; 
                NotifyChanged("DomainSnapshots");
                
            }
        }

        public BacteriumInteractionGraph BacteriumGraph
        {
            get { return _bacteriumGraph; }
            set 
            { 
                _bacteriumGraph = value;
                NotifyChanged("BacteriumGraph");
            }
        }
        
        public BacteriumWatcherViewModel()
        {
            _pollingTimer.Elapsed += new ElapsedEventHandler(_pollingTimer_Elapsed);
            _pollingTimer.AutoReset = true;
        }

        void _pollingTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            GatherBacteria();
        }

        #region Start Command

        private RelayCommand _startCommand;

        public ICommand StartCommand
        {
            get
            {
                if (_startCommand == null)
                {
                    _startCommand = new RelayCommand(p => this.ExecuteStartCommand(), p => this.CanExecuteStartCommand());
                }
                return _startCommand;
            }
        }

        private void ExecuteStartCommand()
        {
           
            _pollingTimer.Start();

            //new Thread(new ThreadStart(GatherBacteria)) 
            //    { IsBackground = true}.Start();
                       
        }

        private Boolean CanExecuteStartCommand()
        {
            return true;
        }

        #endregion

        private void GatherBacteria()
        {            

            var domains = new List<Uri> { new Uri("ldap:///bdid=0"), new Uri("ldap:///bdid=1"), new Uri("ldap:///bdid=2") };

            foreach (var domain in domains)
            {
                var endpoint = DiscoverDomainReporter(domain);
                if (endpoint != null)
                    //new Thread(new ParameterizedThreadStart(CollectDomainBacteria)).Start(endpoint);
                    CollectDomainBacteria(endpoint as Object);
            }
        }

      

        private EndpointAddress DiscoverDomainReporter(Uri scope)
        {
            var discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());

            try
            {
                var criteria = new FindCriteria(typeof(IBacteriumReporter))
                {
                    //Duration = TimeSpan.FromSeconds(2),
                    MaxResults = 1,
                    ScopeMatchBy = FindCriteria.ScopeMatchByExact
                };
                criteria.Scopes.Add(scope);

                var response = discoveryClient.Find(criteria);

                if (response.Endpoints.Count > 0)
                    return response.Endpoints[0].Address;
                else
                    return DiscoverDomainReporter(scope);
            }
            catch (Exception e)
            {
                Console.WriteLine("ERROR: {0}", e.Message);
                return null;
            }
        }


        public void CollectDomainBacteria(Object endpoint)
        {
            //try
            //{
                var reporterProxy = new BacteriumReporterClient(new BasicHttpBinding(), endpoint as EndpointAddress);

                lock (_cs)
                {
                    var result = reporterProxy.GetDomainSnapshot();

                    Bacteria = TryMerging(Bacteria, result);
                    ThreadingHelper.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(RevealInteractions));

                }
            //}
            //catch (Exception e)
            //{

            //    MessageBox.Show(e.Message);
            //}
        }

        private List<BacteriumProfile> TryMerging(List<BacteriumProfile> oldBacteria, List<BacteriumProfile> newBacteria)
        {

            foreach (var bacteriumProfile in newBacteria)
            {
                var duplicateProfiles = oldBacteria.Where(
                    b => b.Snapshot.ID
                    == bacteriumProfile.Snapshot.ID);

                if (duplicateProfiles.Count() > 0)
                {
                    var duplicateProfile = duplicateProfiles.First();

                    if (bacteriumProfile.Events.Keys.Max() > duplicateProfile.Events.Keys.Max())
                    {
                        //If the new bacterium profile is more recent then replace the old one with it
                        var i = oldBacteria.IndexOf(duplicateProfile);
                        oldBacteria.Remove(duplicateProfile);
                        oldBacteria.Insert(i, bacteriumProfile);
                    }
                }
                else
                    oldBacteria.Add(bacteriumProfile);

            }

            return oldBacteria;
        }

        private void RevealInteractions()
        {
            //lock (_cs)
            //{
                foreach (var actorBacterium in _bacteria)
                {

                    //check if current bacterium is already in the list. if so - update its properties, otherwise add it to the vertex list
                    var actorBacterium_ = GetVertexByID(actorBacterium.Snapshot.ID);

                    if (actorBacterium_ == null)
                        BacteriumGraph.AddVertex(actorBacterium.Snapshot);
                    else
                    {
                        actorBacterium_.TeamID = actorBacterium.Snapshot.TeamID;
                        actorBacterium_.Power = actorBacterium.Snapshot.Power;
                        actorBacterium_.CurrentAction = actorBacterium.Snapshot.CurrentAction;                        
                    }

                    //check if action is idle - then remove any outgoing interaction edges and continue with next bacterium
                    var action = actorBacterium.Snapshot.CurrentAction;

                    if (action == BacteriumAction.IDLE)
                    {
                        BacteriumGraph.ClearOutEdges(GetVertexByID(actorBacterium.Snapshot.ID));
                        continue;
                    }

                    //get interaction target by examining id's reported in response to a target request, preceding the most recent action
                    var events = actorBacterium.Events;
                    var targetRequests = events.Where(e => GetBacteriumFromEventResponse(e.Value) != null).ToDictionary(x => x.Key, x => x.Value);
                    
                    var maxTargetRequestTimestamp = targetRequests.Keys.Max();
                    var targetBacterium = GetBacteriumFromEventResponse(events[maxTargetRequestTimestamp]);

                    //if no target - remove any outgoing interaction edges and continue with the next bacterium
                    if (targetBacterium == null)
                    {
                        BacteriumGraph.ClearOutEdges(GetVertexByID(actorBacterium.Snapshot.ID));
                        continue;
                    }

                    //it target is not in the vertex list - add it. we will not update its props now. it will be done later when the loop will pick it as an actor
                    if (GetVertexByID(targetBacterium.Snapshot.ID) == null)
                        BacteriumGraph.AddVertex(targetBacterium.Snapshot);

                    //once again, get source and destination vertices and draw an interaction edge
                    var actorVertex = BacteriumGraph.Vertices.FirstOrDefault(v => v.ID == actorBacterium.Snapshot.ID);
                    var targetVertex = BacteriumGraph.Vertices.FirstOrDefault(v => v.ID == targetBacterium.Snapshot.ID);

                    BacteriumGraph.AddEdge(new BacteriumInteractionEdge(action.ToString(), actorVertex, targetVertex));
                }
            //}
        }

        private BacteriumProfile GetBacteriumFromEventResponse(BacteriumEventDescriptor e)
        {
            return Bacteria.FirstOrDefault(b => b.Snapshot.ID == e.Response);
        }

        private BacteriumSnapshot GetVertexByID(String id)
        {
            return BacteriumGraph.Vertices.FirstOrDefault(v => v.ID == id);
        }


    }
}
